package edu.ssdut.revise.core.match;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.*;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import edu.ssdut.revise.core.matchRule.Bottom2UpMatchRule;
import edu.ssdut.revise.core.matchRule.MatchRule;
import edu.ssdut.revise.core.matchRule.Top2DownMatchRule;
import edu.ssdut.revise.models.CUWithInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * match the code elements, containing two CompilationUnit.
 */
public class TwoFileMatcher {
    private static final Logger logger = LoggerFactory.getLogger(TwoFileMatcher.class);

    CompilationUnit cu1;
    CompilationUnit cu2;
    MatchStore<TypeDeclaration<?>> typeDeclarationMatchStore = new MatchStore<>();
    MatchStore<MethodDeclaration> methodDeclarationMatchStore = new MatchStore<>();
    MatchStore<VariableDeclarator> variableWithinFieldsMatchStore = new MatchStore<>();
    MatchStore<ConstructorDeclaration> constructorDeclarationMatchStore = new MatchStore<>();

    /**
     * constructor with two CUs of two source files.
     */
    public TwoFileMatcher(CompilationUnit cu1, CompilationUnit cu2) {
        this.cu1 = cu1;
        this.cu2 = cu2;

        logger.info("start matching: node gathering");
        //gather nodes
        // considering using TreeVisitor
        TypeDeclarationCollector tdCollector = new TypeDeclarationCollector();
        tdCollector.visit(cu1, typeDeclarationMatchStore.getUnmatchedNodes1());
        tdCollector.visit(cu2, typeDeclarationMatchStore.getUnmatchedNodes2());

        MethodDeclarationCollector mdCollector = new MethodDeclarationCollector();
        mdCollector.visit(cu1, methodDeclarationMatchStore.getUnmatchedNodes1());
        mdCollector.visit(cu2, methodDeclarationMatchStore.getUnmatchedNodes2());

        ConstructorDeclarationCollector cdCollector = new ConstructorDeclarationCollector();
        cdCollector.visit(cu1, constructorDeclarationMatchStore.getUnmatchedNodes1());
        cdCollector.visit(cu2, constructorDeclarationMatchStore.getUnmatchedNodes2());

        VariablesWithinFieldsCollector vdCollector = new VariablesWithinFieldsCollector();
        vdCollector.visit(cu1, variableWithinFieldsMatchStore.getUnmatchedNodes1());
        vdCollector.visit(cu2, variableWithinFieldsMatchStore.getUnmatchedNodes2());

        this.match();
    }

    public TwoFileMatcher(CUWithInfo cui1, CUWithInfo cui2) {
        this(cui1.getCu(), cui2.getCu());
    }

    /**
     * match nodes of the same types.(only one-to-one right now)
     */
    protected void match() {
        logger.info("matching start");

        logger.info("top-down state");
        MatchRule top2DownMatchRule = new Top2DownMatchRule();
        // top-down(match by signature)

        // match type declarations
        top2DownMatchRule.typeDeclarationMatch(typeDeclarationMatchStore);
        // match variable declarators within fields
        top2DownMatchRule.variableDeclaratorMatch(variableWithinFieldsMatchStore);
        // match method declarations
        top2DownMatchRule.methodDeclarationMatch(methodDeclarationMatchStore);
        // match constructor declarations
        top2DownMatchRule.constructorDeclarationMatch(constructorDeclarationMatchStore);

        logger.info("bottom-up state");
        // bottom-up

        MatchRule bottom2UpMatchRule = new Bottom2UpMatchRule();
        // match variable declarators within fields
        bottom2UpMatchRule.variableDeclaratorMatch(variableWithinFieldsMatchStore);
        // match method declarations
        bottom2UpMatchRule.methodDeclarationMatch(methodDeclarationMatchStore);

    }


    public CompilationUnit getCu1() {
        return cu1;
    }

    public CompilationUnit getCu2() {
        return cu2;
    }

    public MatchStore<VariableDeclarator> getVariableWithinFieldsMatchStore() {
        return variableWithinFieldsMatchStore;
    }

    public MatchStore<TypeDeclaration<?>> getTypeDeclarationMatchStore() {
        return typeDeclarationMatchStore;
    }

    public MatchStore<MethodDeclaration> getMethodDeclarationMatchStore() {
        return methodDeclarationMatchStore;
    }

    public MatchStore<VariableDeclarator> getVariableDeclaratorMatchStore() {
        return variableWithinFieldsMatchStore;
    }

    public MatchStore<ConstructorDeclaration> getConstructorDeclarationMatchStore() {
        return constructorDeclarationMatchStore;
    }

    /**
     * Collect all TypeDeclarations(ClassOrInterfaceDeclaration/EnumDeclaration).
     * <br/>Need to be provided with {@code List<TypeDeclaration<?>>} as collector.
     */
    static class TypeDeclarationCollector extends VoidVisitorAdapter<List<TypeDeclaration<?>>> {
        // Class or Interface
        @Override
        public void visit(ClassOrInterfaceDeclaration n, List<TypeDeclaration<?>> unmatchedNode) {
            super.visit(n, unmatchedNode);
            unmatchedNode.add(n);
        }

        // Enum
        @Override
        public void visit(EnumDeclaration n, List<TypeDeclaration<?>> unmatchedNode) {
            super.visit(n, unmatchedNode);
            unmatchedNode.add(n);
        }
    }

    /**
     * Collect all MethodDeclarations (constructors excluded).
     * <br/>Need to be provided with {@code List<MethodDeclaration>} as collector.
     */
    static class MethodDeclarationCollector extends VoidVisitorAdapter<List<MethodDeclaration>> {
        @Override
        public void visit(MethodDeclaration n, List<MethodDeclaration> unmatchedNode) {
            super.visit(n, unmatchedNode);
            unmatchedNode.add(n);
        }
    }

    /**
     * Collect all ConstructorDeclarations.
     * <br/>Need to be provided with {@code List<ConstructorDeclaration>} as collector.
     */
    static class ConstructorDeclarationCollector extends VoidVisitorAdapter<List<ConstructorDeclaration>> {
        @Override
        public void visit(ConstructorDeclaration n, List<ConstructorDeclaration> unmatchedNode) {
            super.visit(n, unmatchedNode);
            unmatchedNode.add(n);
        }
    }


    /**
     * Collect all VariableDeclarators within FieldDeclarations.
     * <br/>Need to be provided with {@code List<VariableDeclarator>} as collector.
     */
    static class VariablesWithinFieldsCollector extends VoidVisitorAdapter<List<VariableDeclarator>> {
        @Override
        public void visit(FieldDeclaration n, List<VariableDeclarator> unmatchedNode) {
            super.visit(n, unmatchedNode);
            unmatchedNode.addAll(new ArrayList<>(n.getVariables()));
        }
    }
}
